/* @flow */

import VNode, { createTextVNode } from 'core/vdom/vnode';
import { isFalse, isTrue, isDef, isUndef, isPrimitive } from 'shared/util';

// The template compiler attempts to minimize the need for normalization by
// statically analyzing the template at compile time.
//
// For plain HTML markup, normalization can be completely skipped because the
// generated render function is guaranteed to return Array<VNode>. There are
// two cases where extra normalization is needed:

/**
 * simpleNormalizeChildren 调用场景是 render 函数是编译生成的，
 * 理论上编译生成的 children 都已经是 VNode 类型的，
 * 但这里有一个例外，就是 functional component 函数式组件返回的是一个数组而不是一个根节点，
 * 所以会通过 Array.prototype.concat 方法把整个 children 数组打平，
 * 让它的深度只有一层。
 * @param {*} children
 */
export function simpleNormalizeChildren(children: any) {
  for (let i = 0; i < children.length; i++) {
    // 如果子元素是数组，需要将数组打平
    // 让他的深度只有一层
    if (Array.isArray(children[i])) {
      return Array.prototype.concat.apply([], children);
    }
  }
  return children;
}

/**
 * normalizeChildren 方法的调用场景有 2 种，
 * 一个场景是 render 函数是用户手写的，
 * 当 children 只有一个节点的时候，
 * Vue.js 从接口层面允许用户把 children 写成基础类型用来创建单个简单的文本节点，
 * 这种情况会调用 createTextVNode 创建一个文本节点的 VNode；
 * 另一个场景是当编译 slot、v-for 的时候会产生嵌套数组的情况，
 * 会调用 normalizeArrayChildren 方法，接下来看一下它的实现：
 * @param {*} children
 */
export function normalizeChildren(children: any): ?Array<VNode> {
  // isPrimitive 是不是基础类型，是的话直接创建文本 VNode
  return isPrimitive(children)
    ? [createTextVNode(children)]
    : //
    Array.isArray(children)
    ? normalizeArrayChildren(children)
    : undefined;
}

function isTextNode(node): boolean {
  return isDef(node) && isDef(node.text) && isFalse(node.isComment);
}

function normalizeArrayChildren(
  children: any,
  nestedIndex?: string
): Array<VNode> {
  const res = [];
  let i, c, lastIndex, last;
  for (i = 0; i < children.length; i++) {
    c = children[i];
    if (isUndef(c) || typeof c === 'boolean') continue;
    lastIndex = res.length - 1;
    last = res[lastIndex];
    //  nested
    if (Array.isArray(c)) {
      if (c.length > 0) {
        // 如果是数组就继续递归调用
        c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`);
        // merge adjacent text nodes
        if (isTextNode(c[0]) && isTextNode(last)) {
          res[lastIndex] = createTextVNode(last.text + (c[0]: any).text);
          c.shift();
        }
        res.push.apply(res, c);
      }
    } else if (isPrimitive(c)) {
      if (isTextNode(last)) {
        // merge adjacent text nodes
        // this is necessary for SSR hydration because text nodes are
        // essentially merged when rendered to HTML strings
        res[lastIndex] = createTextVNode(last.text + c);
      } else if (c !== '') {
        // convert primitive to vnode
        res.push(createTextVNode(c));
      }
    } else {
      if (isTextNode(c) && isTextNode(last)) {
        // merge adjacent text nodes
        res[lastIndex] = createTextVNode(last.text + c.text);
      } else {
        // default key for nested array children (likely generated by v-for)
        if (
          isTrue(children._isVList) &&
          isDef(c.tag) &&
          isUndef(c.key) &&
          isDef(nestedIndex)
        ) {
          c.key = `__vlist${nestedIndex}_${i}__`;
        }
        res.push(c);
      }
    }
  }
  return res;
}
